/*
 *  SSDateDisplayView.m
 *  MESLib
 * 
 * Copyright 2003-2009 Starline Software Ltd
 * 
 * This file is part of MESLib.
 * 
 * MESLib is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * MESLib is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with MESLib. If not, see <http://www.gnu.org/licenses/>.
 */
//  Created by Tobias Haughton on Fri Feb 07 2003.
//


//SSDateDisplayView holds, controlls and displays the SSEBTrackingCells contained within the chart
//it holds the SSEBTrackingCells in 2 arrays trackingCellArrayByDate and trackingCellArrayByXPos
//these hold the same objects but the objects in trackingCellArrayByDate are sorted by date and
//the objects in trackingCellArrayByXPos are sorted by x position. Their positions in these arrays
//are used to determine whether to set their color to red and whether to display the ("...")

#import "SSDateDisplayView.h"
#import "SSChartView.h"
#import "SSSharedNewEBPanelController.h"
#import "SSObjectFilterPanelController.h"
#import "SSOverlapScaleAndRangePanelController.h"
#import "SSInspectorController.h"

@implementation SSDateDisplayView

- (id)initWithFrame:(NSRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
       NSNotificationCenter *nc  = [NSNotificationCenter defaultCenter];
       [nc addObserver:self
              selector:@selector(updateTrackingCellDisplay)
                  name:@"SSDateRowDisplayChanged"
                object:nil];
       [nc addObserver:self
              selector:@selector(updateTrackingCellEllipseAndColor)
                  name:@"SSUpdateTrackingCellEllipseAndColor"
                object:nil];

       trackingCellArrayByDate = [[NSMutableArray alloc] init];
       trackingCellArrayByXPos = [[NSMutableArray alloc] init];
    }

    return self;
}

- (void)drawRect:(NSRect)rect {
   [self setBoundsOrigin:NSMakePoint(NSMinX([chartView visibleRect]), 0)]; // so that we draw the correct info

   float xStart = (NSMinX([chartView visibleRect]) - 100);
   float xStop = (NSMaxX([chartView visibleRect]) + 50);
   
   [[NSColor whiteColor] set];
   [NSBezierPath fillRect:[self bounds]];


   if([self displayMode] == SSMatrixDisplayMode) {
      int i = 0;
      //get the tracking cells to draw themselves
      while (i < [trackingCellArrayByXPos count]) {
         SSEBTrackingCell *trackingCell = [trackingCellArrayByXPos objectAtIndex:i];
         if(NSMinX([trackingCell cellFrame]) > xStart) {
            if(NSMinX([trackingCell cellFrame]) > xStop) {
               //we have passed the drawing area so break
               break;
            } else {
               //it is within the rect we are drawing
               [trackingCell drawInView:self];
            }
         }

         i++;
      }
   } else {
      //overlap mode
      [[NSColor blackColor] set];

      NSMutableDictionary *overlapDisplayData = [(SSChartView *)chartView overlapDisplayData];
      NSDictionary *textAttributes = [overlapDisplayData objectForKey:@"textAttributes"];

      NSBezierPath *separator = [NSBezierPath bezierPath];
      [separator moveToPoint:NSMakePoint(xStart, 15)];
      [separator lineToPoint:NSMakePoint(xStop, 15)];
      [separator setLineWidth:1];

      [[NSGraphicsContext currentContext] setShouldAntialias:NO];
      [separator stroke];

      [[overlapDisplayData objectForKey:@"secondaryPath"] stroke];
      [[NSGraphicsContext currentContext] setShouldAntialias:YES];

      //draw the strings
      NSMutableArray *unitStringArray = [overlapDisplayData objectForKey:@"unitDateStrings"];
      NSMutableArray *secondaryUnitStringArray = [overlapDisplayData objectForKey:@"secondaryUnitDateStrings"];
      NSMutableArray *unitStringPositionArray = [overlapDisplayData objectForKey:@"unitDateStringPositions"];
      NSMutableArray *secondaryUnitStringPositionArray = [overlapDisplayData objectForKey:@"secondaryUnitDateStringPositions"];
      int i = 0;

      while(i < [unitStringArray count]) {
         //draw unit strings
         [[unitStringArray objectAtIndex:i] drawAtPoint:NSMakePoint([[unitStringPositionArray objectAtIndex:i] floatValue], 2) withAttributes:textAttributes];
         i++;
      }

      i = 0;
      while(i < [secondaryUnitStringArray count]) {
         //draw secondary unit strings
         [[secondaryUnitStringArray objectAtIndex:i] drawAtPoint:NSMakePoint([[secondaryUnitStringPositionArray objectAtIndex:i] floatValue], 17) withAttributes:textAttributes];
         i++;
      }
   }
}

//***********************************

- (void)updateDisplayForCell:(NSNotification *)note
{
   if([self displayMode] == SSMatrixDisplayMode) {
      [self setNeedsDisplayInRect:[self visibleRect]];
   }
}

- (void)updateTrackingCellDisplay
{
   if([self displayMode] == SSMatrixDisplayMode) {
      [trackingCellArrayByXPos makeObjectsPerformSelector:@selector(updateDisplayString)];

      [self setTrackingCellEllipseFromIndex:0]; //cell widths have changed so reset the ellipses

      [self setNeedsDisplayInRect:[self visibleRect]];
   }
}

- (void)updateTrackingCellEllipseAndColor
{
   if([self displayMode] == SSMatrixDisplayMode) {
      [self setTrackingCellEllipseFromIndex:0];
      [self setTrackingCellTextColorsFromIndex:0];

      [self setNeedsDisplayInRect:[self visibleRect]];
   }
}

- (void)addTrackingCellForEvent:(SSEventObject *)event
{
   SSEBTrackingCell *newTrackingCell = [[[SSEBTrackingCell alloc] initWithTrackedEvent:event] autorelease];

   [trackingCellArrayByDate addObject:newTrackingCell];
   [trackingCellArrayByXPos addObject:newTrackingCell];
   [self sortTrackingCellArrays];

   //register for notifications from this cell
   NSNotificationCenter *nc  = [NSNotificationCenter defaultCenter];
   [nc addObserver:self
          selector:@selector(sortTrackingCellArrayByDate:)
              name:@"SSSortTrackingCellBydate"
            object:newTrackingCell];
   [nc addObserver:self
          selector:@selector(sortTrackingCellArrayByXPos:)
              name:@"SSSortTrackingCellByPosition"
            object:newTrackingCell];
   [nc addObserver:self
          selector:@selector(updateDisplayForCell:)
              name:@"SSDateDisplayRedrawForCell"
            object:newTrackingCell];
   [self setNeedsDisplayInRect:[newTrackingCell cellFrame]];
}

- (void)removeTrackingCellForEvent:(SSEventObject *)event
{
   NSEnumerator *enumerator = [trackingCellArrayByDate objectEnumerator];
   id anObject;

   while (anObject = [enumerator nextObject]) {
      if([[anObject trackedEvent] isEqualTo:event]) {
         //this is the one we want to remove
         [trackingCellArrayByDate removeObjectIdenticalTo:anObject];
         [trackingCellArrayByXPos removeObjectIdenticalTo:anObject];

         //stop us from getting notifications for this object
         NSNotificationCenter *nc  = [NSNotificationCenter defaultCenter];
         [nc removeObserver:self
                       name:@"SSSortTrackingCellBydate"
                     object:anObject];
         [nc removeObserver:self
                       name:@"SSSortTrackingCellByPosition"
                     object:anObject];
         [nc removeObserver:self
                       name:@"SSDateDisplayRedrawForCell"
                     object:anObject];
         break;
      }
   }
   [self sortTrackingCellArrays];
}

- (void)sortTrackingCellArrays
{
   if([self displayMode] == SSMatrixDisplayMode) {
      [trackingCellArrayByDate sortUsingSelector:@selector(compareDateToTrackingCell:)];
      [trackingCellArrayByXPos sortUsingSelector:@selector(compareXPosToTrackingCell:)];
      [self setTrackingCellTextColorsFromIndex:0];
      [self setTrackingCellEllipseFromIndex:0];
   }
}

- (void)sortTrackingCellArrayByDate:(NSNotification *)note
{
   if([self displayMode] == SSMatrixDisplayMode) {
      [trackingCellArrayByDate sortUsingSelector:@selector(compareDateToTrackingCell:)];
      [self setTrackingCellTextColorsFromIndex:([trackingCellArrayByXPos indexOfObjectIdenticalTo:[note object]] - 1)];
   }
}

- (void)sortTrackingCellArrayByXPos:(NSNotification *)note
{
   if([self displayMode] == SSMatrixDisplayMode) {
      [trackingCellArrayByXPos sortUsingSelector:@selector(compareXPosToTrackingCell:)];
      [self setTrackingCellEllipseFromIndex:([trackingCellArrayByXPos indexOfObjectIdenticalTo:[note object]] - 3)];
      [self setTrackingCellTextColorsFromIndex:([trackingCellArrayByXPos indexOfObjectIdenticalTo:[note object]] - 1)];
   }
}


- (void)setTrackingCellTextColorsFromIndex:(int)index
{
   if([self displayMode] == SSMatrixDisplayMode) {
      if(index < 0) {
         index = 0;
      }

      if([trackingCellArrayByXPos count] > 0) {
         NSRange range = NSMakeRange(index, ([trackingCellArrayByXPos count] - index));
         NSEnumerator *enumerator = [[trackingCellArrayByXPos subarrayWithRange:range] objectEnumerator];
         NSEnumerator *compareEnumerator;
         id anObject;
         id compareObject;
         NSArray *compareArray;
         BOOL setRed;

         while (anObject = [enumerator nextObject]) {
            if(index > 0) {
               setRed = NO;
               compareArray = [trackingCellArrayByXPos subarrayWithRange:NSMakeRange(0, index)]; //get an array with all cells to the left of us
               compareEnumerator = [compareArray objectEnumerator];//get an enumerator for the compare array
                  while (compareObject = [compareEnumerator nextObject]) {
                     if([anObject compareDateToTrackingCell:compareObject] == NSOrderedAscending) { //this object has a higher start dat than us so we are out of order
                        [anObject shouldDisplayInRed:YES];
                        setRed = YES;
                        break;
                     }
                  }
                  if(!setRed) {
                     [anObject shouldDisplayInRed:NO]; //we are in order
                  }
            } else {
               //it is the first cell so it should be black
               [anObject shouldDisplayInRed:NO];
            }
            index++;
         }
      }
   }
}

- (void)setTrackingCellEllipseFromIndex:(int)index
{
   if([self displayMode] == SSMatrixDisplayMode) {
      if(index < 0) {
         index = 0;
      }

      if([trackingCellArrayByXPos count] > 0) {
         NSRange range = NSMakeRange(index, ([trackingCellArrayByXPos count] - index));
         NSEnumerator *enumerator = [[trackingCellArrayByXPos subarrayWithRange:range] objectEnumerator];
         id anObject;
         id lastObject = [trackingCellArrayByXPos lastObject];
         id firstObject = [trackingCellArrayByXPos objectAtIndex:0];
         id compareObject;
         BOOL drawEllipse;

         while (anObject = [enumerator nextObject]) {
            drawEllipse = NO;

            if(![anObject isEqualTo:lastObject]) {
               compareObject = [trackingCellArrayByXPos objectAtIndex:(index + 1)];
               if([self cell:anObject overlapsCell:compareObject]) {
                  //this cell overlaps the next so draw the ellipse
                  drawEllipse = YES;
               }
            }

            if(!drawEllipse) {
               //if we are already going to draw the ellipse there is no need to do the second test
               if(![anObject isEqualTo:firstObject]) {
                  compareObject = [trackingCellArrayByXPos objectAtIndex:(index - 1)];
                  if([self cell:anObject overlapsCell:compareObject]) {
                     //this cell overlaps the last so draw the ellipse
                     drawEllipse = YES;
                  }
               }
            }

            [anObject shouldDrawEllipse:drawEllipse];
            index++;
         }
      }
   }
}

//***********************************

- (BOOL)cell:(SSEBTrackingCell *)firstCell overlapsCell:(SSEBTrackingCell *)secondCell
{
   if([firstCell compareDateToTrackingCell:secondCell] == NSOrderedDescending) {
      //second cell is an eairlier date so it will be above
      return NO;
   }

   if(NSMaxX([firstCell cellFrame]) >= NSMinX([secondCell cellFrame]) && NSMaxX([firstCell cellFrame]) <= NSMaxX([secondCell cellFrame])) {
      //our right hand edge is within the others frame
      return YES;
   }

   if(NSMinX([firstCell cellFrame]) >= NSMinX([secondCell cellFrame]) && NSMinX([firstCell cellFrame]) <= NSMaxX([secondCell cellFrame])) {
      //our left hand edge is within the others frame
      return YES;
   }
   
   return NO; //we don't cover the other
}

- (NSRect)frameForCellAtDateIndex:(int)index
{
   if([trackingCellArrayByDate count] >= index) {
      return [[trackingCellArrayByDate objectAtIndex:index] cellFrame];
   }

   return NSMakeRect(0, 0, 0, 0);
}

//***********************************

- (int)displayMode
{
   return ssDisplayMode;
}

- (void)setDisplayMode:(int)mode
{
   ssDisplayMode = mode;

   if(mode == SSMatrixDisplayMode) {
      //update the cells
      [self sortTrackingCellArrays];
   }

   [self setNeedsDisplayInRect:[self visibleRect]];
}

//***********************************

- (BOOL)isFlipped
{
   return NO;
}

- (BOOL)isOpaque
{
   // State that the view is opaque for optimization purposes.
   return YES;
}

//***********************************
- (SSChartDate *)chartDateForPoint:(NSPoint)point
{ 
   if([self displayMode] == SSMatrixDisplayMode) {
      SSEBTrackingCell *cellAtPoint = [self cellAtPoint:point];
      if(cellAtPoint) {
         return [[cellAtPoint trackedEvent] beginTime];
      }
      //no cell here so return nothing
      return nil;
   } else {
      return [chartView dateForXPosition:point.x isEstimated:YES];
   }
}

//***********************************
- (SSEBTrackingCell *)cellAtPoint:(NSPoint)point
{
   int i = 0;

   while(i < [trackingCellArrayByXPos count]) {
      if(NSPointInRect(point, [[trackingCellArrayByXPos objectAtIndex:i] cellFrame])) {
         //clicked in this cell
         return [trackingCellArrayByXPos objectAtIndex:i];
      }
      i++;
   }

   //didn't click on a cell so return nil
   return nil;
}

//***********************************
//methods used to prevent window ordering
- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent
{
   return YES;
}

- (void)mouseDown:(NSEvent *)theEvent
{
   NSPoint converted = [self convertPoint:[theEvent locationInWindow] fromView:nil];
   
   if([[SSInspectorController sharedInspectorController] receiveClickedDateInfo]) {
      //inspector wants the information about the click
      SSChartDate *date = [self chartDateForPoint:converted];
      if(date) {
         //if in matrix view there may not be a date so we need to check
         [[SSInspectorController sharedInspectorController] userClickedDate:date];
      }
      [NSApp preventWindowOrdering];
   } else if([[SSSharedNewEBPanelController sharedNewEBController] receiveClickedDateInfo]) {
      //new eb panel wants the information about the click
      SSChartDate *date = [self chartDateForPoint:converted];
      if(date) {
         [[SSSharedNewEBPanelController sharedNewEBController] userClickedDate:date];
      }
      [NSApp preventWindowOrdering];
   } else if([[SSObjectFilterPanelController sharedObjectFilterPanelController] receiveClickedDateInfo]) {
      //object filter panel wants the information about the click
      SSChartDate *date = [self chartDateForPoint:converted];
      if(date) {
         [[SSObjectFilterPanelController sharedObjectFilterPanelController] userClickedDate:date];
      }
      [NSApp preventWindowOrdering];
   } else if([[SSOverlapScaleAndRangePanelController sharedOverlapScaleAndRangePanelController] receiveClickedDateInfo]) {
      //overlap filter panel wants the information about the click
      SSChartDate *date = [self chartDateForPoint:converted];
      if(date) {
         [[SSOverlapScaleAndRangePanelController sharedOverlapScaleAndRangePanelController] userClickedDate:date];
      }
      [NSApp preventWindowOrdering];
   }
   
   [super mouseDown:theEvent];
}

- (BOOL)shouldDelayWindowOrderingForEvent:(NSEvent *)theEvent
{
   return YES;   
}

//***********************************
- (void)dealloc
{
   [[NSNotificationCenter defaultCenter] removeObserver:self];

   [trackingCellArrayByDate release];
   [trackingCellArrayByXPos release];
   [super dealloc];
}

@end
